﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using JasLib.Wpf;

namespace JasLib.Interop.Win32
{
    /// <summary>
    /// Drop-in P/Invoke replacement for IntPtr window handle.
    /// Includes implicit typecasts for IntPtr, System.Windows.Window, and System.Windows.Forms.Control.
    /// This class performs no caching; all property accesses translate to immediate Win32 function calls.
    /// Although HWND can be used in P/Invoke functions without the threat of exceptions,
    /// usage of the convenience properties and methods in this class will result in exceptions upon failure.
    /// </summary>
    [DebuggerDisplay("Handle = {_handle}, Text = {Text}, Class = {ClassName}, Id = {Id}")]
    [StructLayout(LayoutKind.Explicit)]
    public struct HWND : System.Windows.Forms.IWin32Window, System.Windows.Interop.IWin32Window
    {
        [FieldOffset(0)]
        private IntPtr _handle;

        public static readonly HWND NULL = default(HWND);
        public static readonly HWND HWND_TOP = 0;
        public static readonly HWND HWND_BOTTOM = 1;
        public static readonly HWND HWND_TOPMOST = -1;
        public static readonly HWND HWND_NOTOPMOST = -2;
        public static readonly HWND HWND_MESSAGE = -3;
        public static readonly HWND HWND_BROADCAST = 0xFFFF;

        static HWND()
        {
            /// Validate basic underlying assumptions.
            Debug.Assert(default(HWND).IsNull && Marshal.SizeOf(typeof(HWND)) == Marshal.SizeOf(typeof(IntPtr)));
            return;
        }

        private HWND(UINT_PTR handle)
        {
            _handle = handle;
            return;
        }

        public string ToString(string strFormat)
        {
            /// HWNDs are limited to 32-bits in precision, so we don't need anything bigger than this.
            return ((uint)_handle).ToString(strFormat);
        }

        public override string ToString()
        {
            /// Standard practice to show HWNDs as capitalized hex.
            return ToString("X");
        }

        public override int GetHashCode()
        {
            /// The handle itself is a unique hash value limited to 32-bits in precision, so this works out perfectly.
            return (int)_handle;
        }

        public override bool Equals(object obj)
        {
            return (obj is HWND) && ((HWND)obj)._handle == _handle;
        }

        public bool IsNull
        {
            get { return (_handle == IntPtr.Zero); }
            set
            {
                if (value)
                    _handle = IntPtr.Zero;
                else if (IsNull)
                    throw new InvalidOperationException("Can't set a null HWND to an arbitrary non-null value.");
            }
        }

        /// <summary>
        /// This is strictly for implementing all forms of IWin32Window.
        /// </summary>
        public IntPtr Handle
        {
            get { return _handle; }
        }

        #region Native Win32 API properties

        public List<HWND> Children
        {
            get
            {
                HWND hThis = this;
                List<HWND> childWindowList = new List<HWND>();
                if (!USER32.EnumChildWindows(this, (hWnd, lParam) => { if (hThis == USER32.GetParent(hWnd)) childWindowList.Add(hWnd); return true; }))
                    throw new Win32Exception();
                return childWindowList;
            }
        }

        public string ClassName
        {
            get
            {
                string strClassName = USER32.GetClassName(this);
                if (strClassName == null)
                    throw new Win32Exception();
                return strClassName;
            }
        }

        public WindowClassStyles ClassStyles
        {
            get { return (WindowClassStyles)(long)USER32.GetClassLong(this, ClassLongOffset.GCL_STYLE); }
            set { USER32.SetClassLong(this, ClassLongOffset.GCL_STYLE, (int)value); }
        }

        /// <summary>
        /// Retrieves the coordinates of a window's client area. The client coordinates specify the upper-left and lower-right corners of the client area. Because client coordinates are relative to the upper-left corner of a window's client area, the coordinates of the upper-left corner are (0,0).
        /// </summary>
        public RECT ClientRect
        {
            get
            {
                RECT rc;
                if (!USER32.GetClientRect(this, out rc))
                    throw new Win32Exception();
                return rc;
            }
        }

        public List<HWND> Descendants
        {
            get
            {
                List<HWND> descendantWindowList = USER32.EnumChildWindows(this);
                if (descendantWindowList == null)
                    throw new Win32Exception();
                return descendantWindowList;
            }
        }

        public HWND EnabledPopup
        {
            get { return USER32.GetWindow(this, GetWindowCode.GW_ENABLEDPOPUP); }
        }

        public HINSTANCE HINSTANCE
        {
            get { return (HINSTANCE)(UINT_PTR)User32Helper.GetWindowLongPtr(this, WindowLongPtrOffset.GWLP_HINSTANCE); }
        }

        public HMONITOR HMONITOR
        {
            get { return USER32.MonitorFromWindow(this, DefaultMonitorCode.MONITOR_DEFAULTTONEAREST); }
        }

        public bool IsActiveWindow
        {
            get { return (USER32.GetActiveWindow() == this); }
            set
            {
                if (value)
                {
                    if (USER32.SetActiveWindow(this) == NULL)
                        throw new Win32Exception();
                }
                else
                    throw new InvalidOperationException("Can't arbitrarily relinquish the active window state.");
            }
        }

        public int Id
        {
            get { return (int)User32Helper.GetWindowLongPtr(this, WindowLongPtrOffset.GWLP_ID); }
            set { User32Helper.SetWindowLongPtr(this, WindowLongPtrOffset.GWLP_ID, value); }
        }

        public bool IsVisible
        {
            get { return USER32.IsWindowVisible(this); }
            set
            {
                if (!USER32.SetWindowPos(this, NULL, 0, 0, 0, 0, value ? SetWindowPosFlags.SWP_SHOWWINDOW : SetWindowPosFlags.SWP_HIDEWINDOW))
                    throw new Win32Exception();
            }
        }

        public bool IsWindow
        {
            get { return USER32.IsWindow(this); }
        }

        public HMENU Menu
        {
            get { return USER32.GetMenu(this); }
            set
            {
                if (!USER32.SetMenu(this, value))
                    throw new Win32Exception();
            }
        }

        public HWND Owner
        {
            get { return USER32.GetWindow(this, GetWindowCode.GW_OWNER); }
        }

        public HWND Parent
        {
            get { return USER32.GetParent(this); }
            set { USER32.SetParent(this, value); }
        }

        public HWND Root
        {
            get { return USER32.GetAncestor(this, GetAncestorCode.GA_ROOT); }
        }

        public HWND RootOwner
        {
            get { return USER32.GetAncestor(this, GetAncestorCode.GA_ROOTOWNER); }
        }

        public string Text
        {
            get
            {
                string strWindowText = USER32.GetWindowText(this);
                if (strWindowText == null)
                    throw new Win32Exception();
                return strWindowText;
            }
            set
            {
                if (!USER32.SetWindowText(this, value))
                    throw new Win32Exception();
            }
        }

        /// <summary>
        /// Gets or sets the dimensions of the bounding rectangle of the specified window. The dimensions are given in screen coordinates that are relative to the screen origin.
        /// </summary>
        public RECT WindowRect
        {
            get
            {
                RECT rc;
                if (!USER32.GetWindowRect(this, out rc))
                    throw new Win32Exception();
                return rc;
            }
            set
            {
                if (!USER32.SetWindowPos(this, NULL, value.left, value.top, value.Width, value.Height, SetWindowPosFlags.SWP_NOZORDER | SetWindowPosFlags.SWP_NOOWNERZORDER))
                    throw new Win32Exception();
            }
        }

        public WindowStyles WindowStyles
        {
            get { return (WindowStyles)(long)User32Helper.GetWindowLongPtr(this, WindowLongPtrOffset.GWLP_STYLE); }
            set { User32Helper.SetWindowLongPtr(this, WindowLongPtrOffset.GWLP_STYLE, (uint)value); }
        }

        public ExtendedWindowStyles ExtendedWindowStyles
        {
            get { return (ExtendedWindowStyles)(long)User32Helper.GetWindowLongPtr(this, WindowLongPtrOffset.GWLP_EXSTYLE); }
            set { User32Helper.SetWindowLongPtr(this, WindowLongPtrOffset.GWLP_EXSTYLE, (uint)value); }
        }

        public uint ProcessId
        {
            get
            {
                uint uiProcessId;
                USER32.GetWindowThreadProcessId(this, out uiProcessId);
                if (uiProcessId == 0)
                    throw new Win32Exception();
                return uiProcessId;
            }
        }

        public uint ThreadId
        {
            get
            {
                uint uiProcessId;
                uint uiThreadId = USER32.GetWindowThreadProcessId(this, out uiProcessId);
                if (uiThreadId == 0)
                    throw new Win32Exception();
                return uiThreadId;
            }
        }

        #endregion

        #region Native Win32 API actions

        public void PostMessage(WindowMessageId messageId, UINT_PTR wParam, INT_PTR lParam)
        {
            if (!USER32.PostMessage(this, messageId, wParam, lParam))
                throw new Win32Exception();
        }

        public INT_PTR SendMessage(WindowMessageId messageId, UINT_PTR wParam, INT_PTR lParam)
        {
            INT_PTR iResult = USER32.SendMessage(this, messageId, wParam, lParam);
            if (KERNEL32.GetLastError() != Win32ErrorCode.ERROR_SUCCESS)
                throw new Win32Exception();
            return iResult;
        }

        #endregion

        #region Operator overloads

        public static bool operator ==(HWND hwnd1, HWND hwnd2)
        {
            return hwnd1._handle == hwnd2._handle;
        }

        public static bool operator !=(HWND hwnd1, HWND hwnd2)
        {
            return hwnd1._handle != hwnd2._handle;
        }

        public static bool operator ==(HWND hwnd1, INT_PTR hwnd2)
        {
            return hwnd1._handle == (IntPtr)hwnd2;
        }

        public static bool operator !=(HWND hwnd1, INT_PTR hwnd2)
        {
            return hwnd1._handle != (IntPtr)hwnd2;
        }

        public static bool operator ==(INT_PTR hwnd1, HWND hwnd2)
        {
            return (IntPtr)hwnd1 == hwnd2._handle;
        }

        public static bool operator !=(INT_PTR hwnd1, HWND hwnd2)
        {
            return (IntPtr)hwnd1 != hwnd2._handle;
        }

        #endregion

        #region Typecasts

        public static implicit operator int(HWND hwnd)
        {
            return hwnd._handle.ToInt32();
        }

        /// <summary>
        /// This typecast is useful because AutomationElement.Current.NativeWindowHandle is declared as an int.
        /// </summary>
        public static implicit operator HWND(int hwnd)
        {
            return new HWND(hwnd);
        }

        public static implicit operator IntPtr(HWND hwnd)
        {
            return hwnd._handle;
        }

        public static implicit operator HWND(IntPtr hwnd)
        {
            return new HWND(hwnd);
        }

        public static implicit operator System.Windows.Window(HWND hwnd)
        {
            if (hwnd.IsNull)
                return null;
            System.Windows.Interop.HwndSource hwndSource = System.Windows.Interop.HwndSource.FromHwnd(hwnd);
            if (hwndSource == null)
                return null;
            return (hwndSource.RootVisual as System.Windows.Window);
        }

        public static implicit operator HWND(System.Windows.FrameworkElement element)
        {
            if (element == null)
                return NULL;

            System.Windows.Window window = element.FindLogicalParent<System.Windows.Window>();
            if (window == null)
                return NULL;

            return (new System.Windows.Interop.WindowInteropHelper(window)).Handle;
        }

        public static implicit operator System.Windows.Forms.Control(HWND hwnd)
        {
            if (hwnd.IsNull)
                return null;
            return System.Windows.Forms.Control.FromHandle(hwnd);
        }

        public static implicit operator HWND(System.Windows.Forms.Control control)
        {
            if (control == null)
                return NULL;
            else
                return control.Handle;
        }

        public static implicit operator System.Windows.Forms.NativeWindow(HWND hwnd)
        {
            if (hwnd.IsNull)
                return null;
            return System.Windows.Forms.NativeWindow.FromHandle(hwnd);
        }

        public static implicit operator HWND(System.Windows.Forms.NativeWindow control)
        {
            if (control == null)
                return NULL;
            else
                return control.Handle;
        }

        public static implicit operator System.Windows.Automation.AutomationElement(HWND hwnd)
        {
            if (hwnd.IsNull)
                return null;
            return System.Windows.Automation.AutomationElement.FromHandle(hwnd);
        }

        public static implicit operator HWND(System.Windows.Automation.AutomationElement element)
        {
            if (element == null)
                return NULL;
            else
                return new HWND(element.Current.NativeWindowHandle);
        }

        /*** These will have to wait until they do away with error CS0552!

        public static implicit operator HWND(System.Windows.Forms.IWin32Window window)
        {
            return new HWND(window.Handle);
        }
        */

        #endregion
    }
}
